#ifndef T_LOGGER_HPP
#define T_LOGGER_HPP

#include <string>
#include <vector>
#include <string_view>
#include <source_location>
#include <source_location>

#define PLATFORM_SERVER

typedef bool(__stdcall* CheckIfShouldWriteCB)(std::wstring& log);

class TLogger {
private:
	static const size_t fileBreakSizeLimit = 1024 * 256;
	//static const size_t fileBreakSizeLimit = 0;

	static std::wstring filenameTXT;
	static bool flagFilenameSet;
	static size_t writtenSizeCount;
	static int breakFileIndex;
	static volatile bool flagBreakingFile;
	static std::wstring breakingBuffer;

	static CheckIfShouldWriteCB checkShouldWrite;

	static bool flagHold;
	static bool flagNextNewLine;
	static bool flagAlsoPrint;
	static std::string holdBuffer;
	static std::string indentStr;
	static std::wstring indentWStr;

	static size_t lastWriteOffsetStart;
	static std::string lastWriteStr;

	static std::vector<std::string> warningNames;
	static std::vector<int> warningCounts;

	bool flagWithTime;
	bool flagWriteVanilla;
	std::string localLoggerName = "";

public:
	// use constructor to add intend!
	// when this is destroyed, the indents are removed
	// the indent is in the form of "-|-|-|" so it can be alligned
	TLogger();
	// flagWriteVanilla not effective now
	TLogger(std::string newContent, bool withTime = false, bool flagWriteVanilla = false);
	~TLogger();

	static void init();
	static void clearWarningCount();

	static void setFile(std::string filenameIn, bool flagErase, bool forceSingleFile = false);
	static void setFile(std::wstring filenameIn, bool flagErase, bool forceSingleFile = false);
	static void setFile(char* filenameIn, bool flagErase, bool forceSingleFile = false);
	static void setFlagAlsoPrint();
	// to avoid accidentally mis line break
	static void setNoNewLine();
	static void write(std::string newContent,
		bool flagSkip = false,
		bool flagWithTime = false,
		int addSpaceToMeetThisLength = -1);
	//static void writeWithTime(std::string newContent, int addSpaceToMeetThisLength = -1);
	static void writeVanilla(std::string newContent);
	static void writeVanilla(std::wstring newContent);
	static void writeLine(std::string newContent);
	static void write(std::wstring newContent,
		bool flagSkip = false,
		bool flagWithTime = false,
		int addSpaceToMeetThisLength = -1);
	static void writeLine(std::wstring newContent);
	static void writeLineP(const wchar_t* newContent);
	// return if printed through cout
	static bool writeLineX(std::string newContent,
		bool flagSkip = false,
		bool flagWithTime = false,
		int addSpaceToMeetThisLength = -1,
		bool withAutoIndent = true);
	static bool writeLineX(std::wstring newContent,
		bool flagSkip = false,
		bool flagWithTime = false,
		int addSpaceToMeetThisLength = -1,
		bool withAutoIndent = true);
	//static void writeLineIf(bool toWrite, std::string newContent, bool withTime = false);
	static void writeWarnning(std::string newContent, bool withTime = true);
	static void writeWarnning(std::wstring newContent, bool withTime = true);
	static void writeWarnningP(const wchar_t* newContent);
	static bool writeFirstNWarnings(std::string warningContent, std::string warningName, int printWarningCount, bool withTime = false);
	// only add 2
	static void addIndent(int indentSpace = 2);
	// only drop 2
	static void dropIndent();
	static void clearIndents();

	static void hold(bool quietly = false);
	static void clear();
	static void flush(bool quietly = false);

	//static void removeFrom(size_t start);

	static std::wstring getFilename();

	static std::string makeWarnning(std::string newContent, bool withTime = true);
	static std::wstring makeWarnning(std::wstring newContent, bool withTime = true);

	static size_t setWriteCallback(CheckIfShouldWriteCB callback);
	static size_t getOffset();
	static bool writeNoProc(std::wstring newContent, bool flush);

	// cannot compile?
	inline static void formatSourceCodeLineAndTimeIfNonZero(
		std::wstring hint, std::wstring objectName,
		std::wstring& errorHintOut,
		long* time, std::wstring& errorLineOut,
		const std::source_location& location = std::source_location::current());
	// cannot compile?
	static void removeError(std::wstring hint, std::wstring objectName,
		std::wstring& errorHintOut,
		long* time, std::wstring& errorLineOut);

private:
	static void concatPadding(std::string& str, int addSpaceToMeetThisLength);
	static void concatPadding(std::wstring& str, int addSpaceToMeetThisLength);
	static void concatTime(std::string& str);
	static void concatTime(std::wstring& str);
	static void concatIndent(std::string& str);
	static void concatIndent(std::wstring& str);

	// return if printed through cout
	static bool writeMain(std::string newContent);
	// every write must go through this for processing, except writeNoProc
	static bool writeMain(std::wstring newContent);

	static bool writeCore(std::wstring newContent);
};

struct TClassLogger {
	TClassLogger(std::string classHintIn, bool withTime = false);
	~TClassLogger();

	void write(std::string newContent,
		bool flagSkip = false,
		bool flagWithTime = false,
		int addSpaceToMeetThisLength = -1);
	void writeLine(std::string content,
		bool flagSkip = false,
		bool flagWithTime = false,
		int addSpaceToMeetThisLength = -1);
	void writeWarning(std::string content);
private:
	std::string classHint;
};

struct TGlobalErrorRecorder {
	static std::vector<int> errorFlags;
	static std::vector<char*> errorHints;

	static void addError(int errorFlag, const char* errorHint);
	static bool anyErrorRaised(const char* errorCheckPos = nullptr);
};

class TPercentageUpdater {
private:
	int totalIterationNum = 1;
	int updateNum = 1;
	int updateInterval = 1;
	std::string progressName;
	bool ignore = true;
	const std::string progressBar = "[...\\(^o^)/...\\(^o^)/...\\(^o^)/...\\(^o^)/...\\(^o^)/";
	std::string progressContentBeforePercentage;
	std::string progressContentAfterPercentage;
	const int progressBarOffset = 2;
	int currentPercentage = 0;
	bool released = false;
public:
	~TPercentageUpdater();
	TPercentageUpdater(int totalIterationNumIn, int updateNumIn, std::string progressNameIn);
	void update(int iterationIdx);
	void completeNow();
};

// any log written during this object's life cycle will be checked if duplicate 
class TLoggerRepeatBlock {
public:
	TLoggerRepeatBlock(std::string code);
	~TLoggerRepeatBlock();

	struct BlockData {
		std::wstring filename;
		std::wstring lastRepData;
		std::wstring newRepData;
		bool flagActive = false;
		bool flagMatchedSoFar = true;
		bool flagRepeating = false;
		int count = 0;
	};

	static bool checkShouldWrite(std::wstring& log);
private:
	std::string code;
};
#endif